/*
 * @Author: xiaosihan
 * @Date: 2024-02-05 09:31:57
 * @Last Modified by: xiaosihan
 * @Last Modified time: 2024-06-07 22:14:53
 */

import { first, isString, throttle } from "lodash";
import {
    BufferAttribute,
    Color,
    Euler,
    InstancedBufferAttribute,
    InstancedMesh,
    Material,
    Matrix4,
    Mesh,
    Object3D,
    Quaternion,
    Vector3,
} from "three";
import MeshBase from "./MeshBase";
import threeUtils from "./threeUtils";
import threeLoader from "./threeLoader";

// 实例模型组对象
export default class InstancedMeshBase extends MeshBase {
    constructor(url?: string) {
        super(url);
    }

    isInstancedGroupMesh = true;

    count: number = 0;

    size = new Vector3(1, 1, 1);

    meshCollect: Record<string, Mesh[]> = {};

    instanceCollect: Record<string, InstancedMesh> = {};

    colorMap = new Map<String, Color>();

    declare children: InstancedMesh[];

    static _position: Vector3 = new Vector3();
    static _scale: Vector3 = new Vector3(1, 1, 1);
    static _rotation: Euler = new Euler();
    static _Quaternion: Quaternion = new Quaternion();
    static _matrix = new Matrix4();

    instanceMatrix = new InstancedBufferAttribute(new Float32Array(0 * 16), 16);

    instanceColor = new InstancedBufferAttribute(new Float32Array(0 * 3), 3);

    // 重置子元素
    reset(...object: Object3D[]) {
        super.clear();
        super.add(...object);
        return this;
    }

    // 重构加载模型的方法
    async loadModel(url: string | File | Array<File>, filename: string = "") {
        //获取模型文件的地址
        const modelurl = await (async () => {
            if (isString(url)) {
                return url;
            } else if (url instanceof File) {
                return url.name;
            } else if (url instanceof Array) {
                let modelurl = "";
                const suffixs = [
                    "zip",
                    "obj",
                    "glb",
                    "gltf",
                    "fbx",
                    "dae",
                    "stl",
                    "usdz",
                ];
                for (let file of url) {
                    if (suffixs.includes(threeUtils.getSuffix(file.name))) {
                        modelurl = file.name;
                    }
                }
                return modelurl;
            }
        })();

        this.url = modelurl || "";

        if (!url) {
            return;
        }

        this.loaded = false;

        // 没有模型并且没有加载任务时就去加载模型
        if (
            !MeshBase.models.get(this.url) &&
            !MeshBase.loadModeling.get(this.url)
        ) {
            MeshBase.loadModeling.set(this.url, true);

            // 万能加载器
            let modal = await threeLoader.loadModel(url, filename);

            MeshBase.models.set(this.url, modal);
            MeshBase.loadModeling.set(this.url, false);
        }

        // 如果模型还在加载中, 就等待模型加载完成
        do {
            await new Promise((resolve) => setTimeout(resolve, 50));
        } while (MeshBase.loadModeling.get(this.url));

        if (MeshBase.models.get(this.url)) {

            const mesh = MeshBase.models.get(this.url)!;
            // // 判断是否有动画
            // this.hasAnimation = mesh.animations.length > 0;

            // if (this.hasAnimation) {
            //     const model = SkeletonUtils.clone(mesh);
            //     this.mixer = new AnimationMixer(model);

            //     this.animationActions = {};
            //     mesh.animations.map(animation => {
            //         const animationAction = this.mixer!.clipAction(animation).play();
            //         animationAction.weight = 0;
            //         this.animationActions[animation.name] = animationAction;
            //     });

            //     this.add(model);
            // } else {
            //     this.add(mesh.clone(true));
            // }

            this.size.copy(threeUtils.getSize(mesh));

            const { meshCollect, instanceCollect } = this;

            mesh.updateMatrixWorld(true);

            // collect all mesh counts, in case there are more than one copy of the same mesh in this group
            mesh.traverse((obj) => {
                const mesh = obj as Mesh;
                if (mesh.isMesh) {
                    mesh.matrixWorld.decompose(
                        mesh.position,
                        mesh.quaternion,
                        mesh.scale
                    );
                    mesh.rotation.setFromQuaternion(mesh.quaternion);
                    mesh.updateMatrix();

                    // same geometry and same material consider as the same mesh
                    const uuid =
                        mesh.geometry.uuid + "/" + (mesh.material as Material).uuid;

                    if (meshCollect[uuid]) {
                        meshCollect[uuid].push(mesh);
                    } else {
                        meshCollect[uuid] = [mesh];
                    }
                } else {
                    obj.position.set(0, 0, 0);
                    obj.scale.set(1, 1, 1);
                    obj.rotation.set(0, 0, 0);
                }
            });

            this.clear();
            // create all instances
            Object.keys(meshCollect).forEach((uuid) => {
                const meshes = meshCollect[uuid];
                const mesh = meshes[0];
                const instancedMesh = new InstancedMesh(
                    mesh.geometry,
                    mesh.material,
                    meshes.length * this.count
                );
                instancedMesh.name = mesh.name;
                instancedMesh.frustumCulled = false;
                instanceCollect[uuid] = instancedMesh;
                this.add(instancedMesh);
            });

            // 加载完成事件
            await new Promise((resolve) => setTimeout(resolve, 30));
            // 加载完成事件
            this.loaded = true;
            this.dispatchEvent({ type: "loaded" } as never);
        }
    }

    // 更新包围盒
    computeBoundingSphere = throttle(() => {
        this.children.map((instancedMesh) => {
            instancedMesh.computeBoundingSphere && instancedMesh.computeBoundingSphere();
        });
    }, 1000);

    //初始实例矩阵的数量
    async setCount(count: number = 0) {
        if (this.count === count) {
            return;
        }
        await this.awaitLoaded();

        const instanceMatrix = new InstancedBufferAttribute(
            new Float32Array(count * 16),
            16
        );
        const instanceColor = new InstancedBufferAttribute(
            new Float32Array(count * 3).fill(1),
            3
        );

        for (let i = 0; i < count; i++) {
            instanceMatrix.set(
                InstancedMeshBase._matrix.identity().toArray(),
                i * 16
            );
            instanceColor.set([1, 1, 1], i * 3);
        }

        for (let i = 0; i < this.count; i++) {
            instanceMatrix.copyAt(i, this.instanceMatrix, i);
            instanceColor.copyAt(i, this.instanceColor, i);
        }

        this.instanceMatrix = instanceMatrix;
        this.instanceColor = instanceColor;

        this.count = count;

        this.children.map((instanceMesh: InstancedMesh) => {
            const instanceMatrix = new InstancedBufferAttribute(
                new Float32Array(count * 16),
                16
            );
            const instanceColor = new InstancedBufferAttribute(
                new Float32Array(count * 3).fill(1),
                3
            );

            for (let i = 0; i < count; i++) {
                instanceMatrix.set(
                    InstancedMeshBase._matrix.identity().toArray(),
                    i * 16
                );
                instanceColor.set([1, 1, 1], i * 3);
            }

            for (let i = 0; i < instanceMesh.count; i++) {
                instanceMatrix.copyAt(i, instanceMesh.instanceMatrix, i);
                instanceColor.copyAt(
                    i,
                    instanceMesh.instanceColor as BufferAttribute,
                    i
                );
            }

            instanceMesh.instanceMatrix = instanceMatrix;
            instanceMesh.instanceColor = instanceColor;
            instanceMesh.count = count;
        });
    }

    // 设置矩阵
    async setMatrixAt(index: number, matrix: Matrix4 = new Matrix4().identity()) {
        const new_matrix = matrix.clone(); // 把矩阵克隆一下避免对象引用问题
        await this.awaitLoaded();
        Object.keys(this.meshCollect).forEach((uuid) => {
            const instancedMesh = this.instanceCollect[uuid];
            const collect = this.meshCollect[uuid];
            collect.forEach((mesh: Mesh, i: number) => {
                InstancedMeshBase._matrix.copy(mesh.matrix);
                InstancedMeshBase._matrix.premultiply(new_matrix);

                instancedMesh.setMatrixAt(
                    collect.length * index + i,
                    InstancedMeshBase._matrix
                );
                instancedMesh.instanceMatrix.needsUpdate = true;
            });
        });

        this.computeBoundingSphere();
    }

    //设置移动旋转缩放
    async setTRS(
        index: number,
        {
            position,
            rotation,
            scale,
        }: {
            position?: Vector3;
            rotation?: Euler;
            scale?: Vector3;
        }
    ) {
        const new_position = position ? position.clone() : undefined;
        const new_rotation = rotation ? rotation.clone() : undefined;
        const new_scale = scale ? scale.clone() : undefined;

        await this.awaitLoaded();

        Object.keys(this.meshCollect).forEach((uuid) => {
            const instancedMesh = this.instanceCollect[uuid];
            const collect = this.meshCollect[uuid];
            collect.forEach((mesh: Mesh, i: number) => {
                //获取对应的模型的矩阵
                instancedMesh.getMatrixAt(
                    collect.length * index + i,
                    InstancedMeshBase._matrix
                );
                InstancedMeshBase._matrix.premultiply(mesh.matrix.clone().invert());

                InstancedMeshBase._matrix.decompose(
                    InstancedMeshBase._position,
                    InstancedMeshBase._Quaternion,
                    InstancedMeshBase._scale
                );
                InstancedMeshBase._rotation.setFromQuaternion(
                    InstancedMeshBase._Quaternion
                );
                new_position && InstancedMeshBase._position.copy(new_position);
                new_rotation && InstancedMeshBase._rotation.copy(new_rotation);
                new_scale && InstancedMeshBase._scale.copy(new_scale);
                InstancedMeshBase._Quaternion.setFromEuler(InstancedMeshBase._rotation);

                InstancedMeshBase._matrix.compose(
                    InstancedMeshBase._position,
                    InstancedMeshBase._Quaternion,
                    InstancedMeshBase._scale
                );

                InstancedMeshBase._matrix.premultiply(mesh.matrix);
                instancedMesh.setMatrixAt(
                    collect.length * index + i,
                    InstancedMeshBase._matrix
                );
                instancedMesh.instanceMatrix.needsUpdate = true;
            });
        });

        this.computeBoundingSphere();
    }

    //获取指定位置的移动旋转缩放
    async getTRS(index: number = 0) {

        await this.awaitLoaded();

        const position = new Vector3();
        const rotation = new Euler();
        const scale = new Vector3();

        const uuid = first(Object.keys(this.instanceCollect));
        const instancedMesh = this.instanceCollect[uuid];
        if (instancedMesh) {
            instancedMesh.getMatrixAt(
                index,
                InstancedMeshBase._matrix
            );
            InstancedMeshBase._matrix.decompose(
                position,
                InstancedMeshBase._Quaternion,
                scale
            );
            rotation.setFromQuaternion(InstancedMeshBase._Quaternion);
        }
        return { position, rotation, scale };
    }

    // 设置颜色
    async setColorAt(
        index: number,
        color: (instancedMesh: InstancedMesh) => Color | undefined
    ) {
        await this.awaitLoaded();
        Object.keys(this.meshCollect).forEach((uuid) => {
            const instancedMesh = this.instanceCollect[uuid];
            const collect = this.meshCollect[uuid];
            collect.forEach((mesh: Mesh, i: number) => {
                const c = color(instancedMesh);
                if (c) {
                    instancedMesh.setColorAt(collect.length * index + i, c);
                    instancedMesh.instanceColor!.needsUpdate = true;
                }
            });
        });
    }

    // 获取颜色
    async getColorAt(index: number = 0) {
        await this.awaitLoaded();
        const color = new Color();
        const uuid = first(Object.keys(this.instanceCollect));
        const instancedMesh = this.instanceCollect[uuid];
        if (instancedMesh) {
            instancedMesh.getColorAt(index, color);
        }
        return color;
    }

    // 同步属性
    updateMatrixWorld(force?: boolean): void {
        super.updateMatrixWorld(force);
        const { cursor, enableEvent } = this.userData;
        this.traverse((obj) => {
            Object.assign(obj.userData, { cursor, enableEvent });
        });
    }

    // 事件监听
    async addEventListener(name: string, callback: (e: any) => void) {
        super.addEventListener(name, callback);
        await this.awaitLoaded();
        super.addEventListener(name, callback);
        this.children.map((mesh) => {
            mesh.addEventListener(name, callback);
        });
    }

    // 取消事件的监听
    removeEventListener(name: string, callback: (e: any) => void) {
        super.removeEventListener(name, callback);
        this.children.map((mesh) => {
            mesh.removeEventListener(name, callback);
        });
    }
}
